package cn.jervain.nut.auth.core;

import cn.jervain.nut.auth.core.annotation.HasAcess;
import cn.jervain.nut.auth.core.exception.AuthException;
import cn.jervain.nut.auth.core.provider.AuthProvider;
import cn.jervain.nut.auth.core.storage.TokenStorage;
import cn.jervain.nut.auth.core.token.Token;
import cn.jervain.nut.auth.core.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@SuppressWarnings("ALL")
public class AuthFilter implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private AntPathMatcher matcher = new AntPathMatcher();

    private String key = "Authorization";
    private String prefix = "Bearer ";
    private List<String> permits = new ArrayList<>();
    private Class tokenDetailCls = Object.class;

    @Autowired
    private TokenStorage tokenStorage;
    @Autowired
    private AuthProvider authProvider;


    public AuthFilter() {}

    public AuthFilter(List<String> permits, String key, String prefix) {
        this.permits = permits;
        this.key = key;
        this.prefix = prefix;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            logger.debug(String.format("过滤请求的URI为%s", request.getRequestURI()));
            // 判断是否需要处理登陆请求
            if (authProvider.isLogin(request)) {
                logger.debug("进入登陆请求处理");
                authProvider.auth(request, response);
                return false;
            }
            // 判断是否是允许的地址
            if (!needAuth(request)) {
                logger.debug(String.format("%s地址不需要进行认证", request.getRequestURI()));
                return true;
            }
            String tokenSign = fetchTokenSign(request);
            logger.debug(String.format("获得token标识字符串为%s", tokenSign));
            Token token = tokenStorage.get(tokenSign);
            additionalCheck(token);

            if (handler instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                accessCheck(token, handlerMethod.getMethod());
            }

            return true;
        } catch (AuthException e) {
            e.printStackTrace();
        }
        return true;
    }


    private void accessCheck(Token token, Method method) throws AuthException {
        Assert.notNull(method);
        HasAcess hasAcess = method.getAnnotation(HasAcess.class);
        if (hasAcess == null) {
            return;
        }
        List<String> obtainedAccess = token.getGrantedAccesses();
        if (Arrays.stream(hasAcess.name()).anyMatch(it -> !obtainedAccess.contains(it))) {
            throw new AuthException(403, "没有执行权限");
        }
    }


    private void additionalCheck(Token token) throws AuthException {
        if (token.isAccountLocked()) {
            throw new AuthException(401, "账户已被锁定");
        }
        LocalDateTime grantedExpiredAt = Optional.ofNullable(token.getGrantedExpireAt()).orElse(LocalDateTime.now());
        if (LocalDateTime.now().isAfter(grantedExpiredAt)) {
            throw new AuthException(401, "Token已过期");
        }
    }


    private String fetchTokenSign(HttpServletRequest request) throws AuthException {
        // 首先尝试从Header去查找，否则从参数去查找
        String token = Optional.ofNullable(request.getHeader(key)).orElse(request.getParameter(key));
        if (token == null) {
            throw new AuthException(401, "访问资源需要被认证");
        }
        return token.replace(prefix, "");
    }

    private boolean needAuth(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (permits.stream().anyMatch(it -> matcher.match(it, uri))) {
            return false;
        }
        return true;
    }


}
